﻿using System.Diagnostics.Contracts;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using d_rail.lib.Model;


namespace d_rail.lib.Parsers
{
    public class RouteParser
    {
        public IList<Route> Parse(string path, IList<Track> tracks)
        {
            Contract.Requires(null != tracks);
            Contract.Ensures(Contract.Result<IList<Route>>() != null);
            var routes = new List<Route>();
            using (var file = new StreamReader(path))
            {
                string line;
                while ((line = file.ReadLine()) != null)
                {
                    var tokens = TokenizeLine(line);
                    routes.Add(LineToRoute(tokens, tracks));
                }
            }

            ValidateCollissions(routes);

            return routes;
        }

        private static void ValidateCollissions(IList<Route> routes)
        {
            Contract.Requires(null != routes);
            foreach (var route in routes)
            {
                var route2 = route;
                foreach (var route1 in routes.Where(route1 => route2.GetHashCode() != route1.GetHashCode()))
                {
                    for (var i = 0; i < route.RouteParts.Count; i++)
                    {
                        if (i >= route1.RouteParts.Count)
                            break;

                        //if trains are on same track at the same time
                        Contract.Assert(!route.RouteParts.ElementAt(i).TrackId.Equals(route1.RouteParts.ElementAt(i).TrackId));
                    }
                }
            }
        }

        private static Route LineToRoute(IList<string> tokens, IList<Track> tracks)
        {
            Contract.Requires(null != tokens);
            Contract.Requires(tokens.First().Equals("ROUTE"));
            Contract.Requires(tokens.Count() >= 3);
            Contract.Ensures(Contract.Result<Route>() != null);
            Contract.Ensures(Contract.Result<Route>().RouteParts.Count >= 2);
            var route = new Route();
            string current = null;
            bool? reverse = null;
            var foundSwitch = false;

            foreach (var token in tokens)
            {
                switch (token)
                {
                    case "ROUTE":
                        break;
                    case "STOP":
                        Contract.Assert(tokens.Count() > tokens.IndexOf(current) + 1);
                        string stationId;
                        if (string.IsNullOrEmpty(current))
                        {
                            stationId = tokens.ElementAt(tokens.IndexOf("ROUTE") + 2); // first routepart is a stop at a station ?
                        }
                        else
                        {
                            stationId = tokens.ElementAt(tokens.IndexOf(current) + 2);
                        }
                        var foundStation = false;
                        foreach (var track in tracks)
                        {
                            if (track is Station)
                            {
                                var station = track as Station;
                                if (station.Id.Equals(stationId))
                                    foundStation = true;
                                if (foundStation)
                                {
                                    var newPart = new RoutePart
                                    {
                                        Stop = true,
                                        TrackId = stationId
                                    };
                                    route.RouteParts.AddLast(newPart);
                                    break;   
                                }
                            }
                        }
                        Contract.Assert(foundStation);
                        break;
                    default:
                        if (string.IsNullOrEmpty(current))
                        {
                            current = token;
                            break;
                        }
                        if (foundSwitch)
                        {
                            current = token;
                            foundSwitch = false;
                            break;
                        }
                        var previous = current;
                        current = token;
                        var foundPart = false;
                        foreach (var track in tracks.Where(track => !(track is End) && !(track is Station)))
                        {
                            if (track is Straight)
                            {
                                var straight = track as Straight;
                                if ((straight.SourceId.Equals(current) || straight.SourceId.Equals(previous)) &&
                                    (straight.TargetId.Equals(current) || straight.TargetId.Equals(previous)))
                                {
                                    if (!reverse.HasValue)
                                    {
                                        reverse = straight.SourceId.Equals(current);
                                    }

                                    //if assertion is true, the route is borked
                                    Contract.Assert(reverse.Value && (straight.SourceId.Equals(current) && straight.TargetId.Equals(previous)));

                                    var newPart = new RoutePart
                                    {
                                        Stop = false,
                                        TrackId = track.Id
                                    };
                                    route.RouteParts.AddLast(newPart);
                                    foundPart = true;
                                    break;
                                }
                            }
                            else if (track is Switch)
                            {
                                var swi = track as Switch;
                                if ((tokens.Count() < tokens.IndexOf(current) + 1 && !tokens.ElementAt(tokens.IndexOf(current) + 1).Equals("STOP")) ||
                                    (tokens.Count() < tokens.IndexOf(current) + 2 && tokens.ElementAt(tokens.IndexOf(current) + 1).Equals("STOP"))
                                    )
                                    continue; // we know we are not looking for a switch, since it would cause index out of bounds..
                                if (swi.Id.Equals(previous) || swi.Id.Equals(current))
                                {
                                    var next = (tokens.ElementAt(tokens.IndexOf(current) + 1).Equals("STOP") ? tokens.ElementAt(tokens.IndexOf(current) + 2) : tokens.ElementAt(tokens.IndexOf(current) + 1));
                                    var currentT = (swi.Id.Equals(current) ? next : current);
                                    var previousT = (swi.Id.Equals(previous) ? next : previous);

                                    if ((swi.SourceId.Equals(currentT) &&
                                         (swi.TargetId1.Equals(previousT) || swi.TargetId2.Equals(previousT))) ||
                                        (swi.SourceId.Equals(previousT) &&
                                         (swi.TargetId1.Equals(currentT) || swi.TargetId2.Equals(currentT))))
                                    {
                                        if (!reverse.HasValue)
                                        {
                                            reverse = swi.SourceId.Equals(currentT);
                                        }
                                        Contract.Assert(
                                            (reverse.Value && (swi.SourceId.Equals(currentT) &&
                                                               (swi.TargetId1.Equals(previousT) || swi.TargetId2.Equals(previousT)))) ||
                                            (!reverse.Value && (swi.SourceId.Equals(previousT) &&
                                                                (swi.TargetId1.Equals(currentT) || swi.TargetId2.Equals(currentT))))
                                            );
                                        var newPart = new RoutePart
                                        {
                                            Stop = false,
                                            TrackId = track.Id
                                        };
                                        route.RouteParts.AddLast(newPart);
                                        foundPart = true;
                                        foundSwitch = true;
                                        break;
                                    }

                                }
                            }
                        }
                        Contract.Assert(foundPart);
                        break;
                }
            }

            return route;
        }

        public static string[] TokenizeLine(string line)
        {
            Contract.Requires(!string.IsNullOrEmpty(line));
            Contract.Ensures(Contract.Result<string[]>() != null);
            Contract.Ensures(Contract.Result<string[]>().Any());
            Contract.Ensures(1 <= Contract.Result<string[]>().Length);

            //trim spaces
            line = Regex.Replace(line, @"\s+", " ");
            //split
            return line.Split(' ');
        }

    }
}
